Skip to main content

I2C

The examples in this chapter use the I2C device: 1.54inch Touch LCD Module

1. I2C Subsystem

The Luckfox Lume I2C interfaces are called TWI in the SDK. This chapter uses TWI5, with the device node /dev/i2c-5.

  • /sys/bus/i2c/devices/: Lists I2C adapters and registered slave devices.
  • /dev/i2c-*: Provides user-space access to I2C buses.
  • i2cdetect, i2cget: Probe device addresses and read registers.

2. I2C Testing (Shell)

2.1 Pinout

Physical PinMultiplexed FunctionGPIODescription
3TWI5-SDAPD21Data line
5TWI5-SCLPD20Clock line
1 or 173.3V-Peripheral power supply
6, 9, etc.GND-Common ground

Luckfox Lume 40-pin pinout

2.2 Viewing Devices

On Linux, the /sys/bus/i2c/devices/ directory contains all I2C bus adapters and attached I2C slave device nodes.

  1. List the I2C buses registered in the system:
    root@luckfox:~# ls /sys/bus/i2c/devices/
    5-0045 5-005d i2c-5
  2. View I2C device nodes and bus names:
    root@luckfox:~# ls /dev/i2c-*
    /dev/i2c-5
    root@luckfox:~# i2cdetect -l
    i2c-5 i2c SUNXI TWI(0x02515000) I2C adapter

Directories use two naming formats:

  • I2C bus adapters (controllers): Named i2c-X, where X is the I2C bus number. For example, i2c-1 is I2C bus 1.
  • I2C slave peripherals: Named X-YYYY, where X is the bus number and YYYY is the slave device's hexadecimal address.

2.3 I2C Testing

  1. List devices on the i2c-5 interface:

    i2cdetect -a -y 5

    Hexadecimal values in the scan results are slave device addresses. -- means no device was detected, and UU means the address is already in use by a kernel driver.

  2. Read all registers of the device at address 0x15:

    i2cdump -f -y 5 0x15
  3. Read a specific register of an I2C device, such as register 0xA7 of the device at address 0x15:

    i2cget -f -y 5 0x15 0xA7
  4. Write 0x6f to register 0xA7:

    i2cset -f -y 5 0x15 0xA7 0x6f

Before scanning or writing registers, check the device manual for the address and register definitions to avoid unintended changes to the device state.

3. I2C Communication (Python)

  1. Complete code: The following example reads register 0xA7 from the device at address 0x15 on I2C-5 (TWI5). It submits two messages in a single I2C_RDWR operation: first sending the register index, then reading one byte with a repeated START.

    #!/usr/bin/env python3
    import ctypes
    import errno
    import fcntl
    import os
    import sys

    I2C_BUS = 5
    I2C_ADDR = 0x15
    REG_ADDR = 0xA7

    I2C_SLAVE = 0x0703
    I2C_RDWR = 0x0707
    I2C_M_RD = 0x0001

    class I2CMsg(ctypes.Structure):
    _fields_ = [
    ("addr", ctypes.c_uint16),
    ("flags", ctypes.c_uint16),
    ("len", ctypes.c_uint16),
    ("buf", ctypes.POINTER(ctypes.c_uint8)),
    ]

    class I2CRdwrData(ctypes.Structure):
    _fields_ = [
    ("msgs", ctypes.POINTER(I2CMsg)),
    ("nmsgs", ctypes.c_uint32),
    ]

    def read_register(fd, address, register):
    if not 0x03 <= address <= 0x77:
    raise ValueError("Expected a non-reserved 7-bit I2C address")
    if not 0 <= register <= 0xFF:
    raise ValueError("Expected an 8-bit register address")

    fcntl.ioctl(fd, I2C_SLAVE, address)

    tx = (ctypes.c_uint8 * 1)(register)
    rx = (ctypes.c_uint8 * 1)()
    messages = (I2CMsg * 2)(
    I2CMsg(address, 0, 1, tx),
    I2CMsg(address, I2C_M_RD, 1, rx),
    )
    transfer = I2CRdwrData(messages, 2)

    argument = bytearray(bytes(transfer))
    completed = fcntl.ioctl(fd, I2C_RDWR, argument, True)
    if completed != 2:
    raise OSError(errno.EIO,
    f"Incomplete I2C transfer: {completed}/2 messages")
    return rx[0]

    def main():
    fd = None
    try:
    fd = os.open(f"/dev/i2c-{I2C_BUS}", os.O_RDWR)
    value = read_register(fd, I2C_ADDR, REG_ADDR)
    print(f"0x{I2C_ADDR:02X}[0x{REG_ADDR:02X}] = 0x{value:02X}")
    return 0
    except (OSError, ValueError) as error:
    print(f"I2C communication failed: {error}", file=sys.stderr)
    return 1
    finally:
    if fd is not None:
    os.close(fd)

    if __name__ == "__main__":
    sys.exit(main())
  2. Open the device and select the address:

    fd = os.open(f"/dev/i2c-{I2C_BUS}", os.O_RDWR)

    Open /dev/i2c-5 for reading and writing. Then check whether the address is available in read_register():

    fcntl.ioctl(fd, I2C_SLAVE, address)

    address is the 7-bit address 0x15 and does not need to be shifted left. An error is returned if a driver already owns the address.

  3. Read the register:

    messages = (I2CMsg * 2)(
    I2CMsg(address, 0, 1, tx),
    I2CMsg(address, I2C_M_RD, 1, rx),
    )
    transfer = I2CRdwrData(messages, 2)
    argument = bytearray(bytes(transfer))
    completed = fcntl.ioctl(fd, I2C_RDWR, argument, True)

    The first message sends the register index, and the second reads one byte, with a repeated START between them. Sending 0xA7 selects the register to read; it does not write register data.

  4. Run the program:

    python3 IIC.py

    Output:

4. I2C Communication (C)

  1. Complete code:

    #include <errno.h>
    #include <fcntl.h>
    #include <linux/i2c-dev.h>
    #include <linux/i2c.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <unistd.h>

    #define I2C_DEVICE "/dev/i2c-5"
    #define I2C_ADDRESS 0x15
    #define REG_ADDRESS 0xA7

    int main(void)
    {
    uint8_t reg = REG_ADDRESS, value = 0;
    struct i2c_msg messages[2] = {
    { .addr = I2C_ADDRESS, .flags = 0, .len = 1, .buf = &reg },
    { .addr = I2C_ADDRESS, .flags = I2C_M_RD, .len = 1, .buf = &value },
    };
    struct i2c_rdwr_ioctl_data transfer = {
    .msgs = messages, .nmsgs = 2,
    };
    int fd = open(I2C_DEVICE, O_RDWR);
    if (fd < 0) {
    fprintf(stderr, "open %s failed: %s\n",
    I2C_DEVICE, strerror(errno));
    return EXIT_FAILURE;
    }
    if (ioctl(fd, I2C_SLAVE, I2C_ADDRESS) < 0) {
    fprintf(stderr, "I2C address selection failed: %s\n", strerror(errno));
    close(fd);
    return EXIT_FAILURE;
    }
    int completed = ioctl(fd, I2C_RDWR, &transfer);
    if (completed < 0) {
    fprintf(stderr, "I2C transfer failed: %s\n", strerror(errno));
    close(fd);
    return EXIT_FAILURE;
    }
    if (completed != 2) {
    fprintf(stderr, "Incomplete I2C transfer: %d/2 messages\n", completed);
    close(fd);
    return EXIT_FAILURE;
    }
    printf("0x%02X[0x%02X] = 0x%02X\n",
    I2C_ADDRESS, REG_ADDRESS, value);
    close(fd);
    return EXIT_SUCCESS;
    }
  2. Read the register:

    struct i2c_msg messages[2] = {
    { .addr = I2C_ADDRESS, .flags = 0, .len = 1, .buf = &reg },
    { .addr = I2C_ADDRESS, .flags = I2C_M_RD, .len = 1, .buf = &value },
    };
    struct i2c_rdwr_ioctl_data transfer = {
    .msgs = messages, .nmsgs = 2,
    };

    The first message sends 0xA7, and the second receives one byte. Call ioctl(fd, I2C_RDWR, &transfer) to submit the combined transaction. If it returns 2, retrieve the result from value. Call close(fd) before exiting to release the device.

  3. Cross-compile:

    export PATH=<Luckfox_Lume_SDK>/out/toolchain/gcc-linaro-11.3.1-2022.06-x86_64_arm-linux-gnueabihf/bin:$PATH
    arm-linux-gnueabihf-gcc -Wall -Wextra -O2 IIC.c -o IIC
  4. Transfer and run:

    scp IIC root@<LUME_IP>:/root/

    Run on the board:

    chmod +x /root/IIC
    /root/IIC

    Output: